((global, factory) => {
  typeof exports === "object" && typeof module !== "undefined"
    ? (module.exports = factory(global))
    : typeof define === "function" && define.amd
      ? define(() => factory(global))
      : ((global = typeof globalThis !== "undefined" ? globalThis : global),
        (global.mfoElement = factory(global)));
})(window, (global) => {
  class CustomElement extends HTMLElement {
    constructor() {
      super();
      const template = document.createElement("template");
      template.innerHTML = `<slot></slot>`;
      const templateContent = template.content;
      const shadowRoot = this.attachShadow({ mode: "open" });
      this._shadowRoot = shadowRoot;
      this.root = document.createElement("div");
      this.root.append(templateContent.cloneNode(true));
      shadowRoot.append(this.root);
    }
    connectedCallback() {
      const attrs = this.attributes;
      Object.keys(attrs).forEach((attr) => {
        this.root.setAttribute(attrs[attr].name, attrs[attr].value);
        this.removeAttribute(attrs[attr].name);
      });
    }
  }
  const humpToStr = (str) => {
    str = str.split("");
    str.forEach((a, i) => {
      if (a.toUpperCase() === a) str[i] = "-" + a.toLowerCase();
    });
    return str.join("");
  };

  const createStyle = (ops) => {
    let json = [];
    Object.keys(ops).forEach((style) => {
      json.push([humpToStr(style), ":", ops[style]].join(""));
    });
    return json.join(";");
  };

  class StyleSheet {
    constructor(ops) {
      this.style = ops
      return this
    }
    _style = undefined;
    get style() {
      return this._style
    }
    set style(val) {
      this._style = createStyle(val);
    }
  }

  const hasOwn = {}.hasOwnProperty;
  const isPlainObject = function (obj) {
    var key;
    if (!obj || typeof obj !== "object" || obj.nodeType) {
      return false;
    }
    try {
      if (
        obj.constructor &&
        !hasOwn.call(obj, "constructor") &&
        !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")
      ) {
        return false;
      }
    } catch (e) {
      return false;
    }
    for (key in obj) {
    }
    return key === undefined || hasOwn.call(obj, key);
  };
  class Ref {
    constructor(ops) {
      const that = this;
      if (isPlainObject(ops)) {
        Object.keys(ops).forEach(val => {
          if (
            ops[val].type &&
            ops[val].default &&
            typeof ops[val].type() !== typeof ops[val].default()
          ) {
            console.error(typeof ops[val].type(), typeof ops[val].default());
            return;
          }
          this.options = ops;
          this['_' + val] = this.options[val].default();
          Object.defineProperty(this, [val], {
            configurable: true,
            enumerable: true,
            get() {
              return this['_' + val]
            },
            set(nval) {
              this['_' + val] = nval;
              that.updatedEventHandle()
            }
          })
        })
      }
      return this;
    }
    updatedEventHandle = () => { };
    updated(callback) {
      const that = this;
      that.updatedEventHandle = () => {
        typeof callback === "function" && callback();
      };
      that.updatedEventHandle();
      return {
        trigger() {
          that.updatedEventHandle();
        },
      };
    }
  }

  class Element {
    constructor(ops) {
      this.options = ops;
      return this;
    }
    _options = Object.create(null);
    get options() {
      return this._options
    }
    set options(val) {
      Object.assign(this._options, val);
      if (!this.elem) this.elem = global.document.createElement(this.options.tagName || 'div');

      Object.keys(val).forEach(attr => {
        switch (attr) {
          case 'class':
            this.elem.className = val[attr]
            break;
          case 'style':
            this.elem && (this.elem.style.cssText = val[attr]);
            break;
          case 'text':
            if (this.elem.childNodes[0] && this.elem.childNodes[0].nodeType === 3) {
              this.elem.childNodes[0].textContent = val[attr];
            } else {
              const textElem = global.document.createTextNode(val[attr])
              this.elem.append(textElem);
            }
            break;
          case 'ref':
            refs.push({ name: val[attr], elem: this.elem })
            break;
          case 'components':
            val[attr].forEach(component => this.elem.append(component));
            break;
          case 'slots':
            break;
          default:
            if (/^on/.test(attr)) {
              new Lit(this.elem).on(attr.replace(/^on/gim, ''), val[attr]);
            } else if (!/tagName/.test(attr)) {
              this.elem.setAttribute(attr, val[attr])
            }
            break;
        }
      })

      const observer = new MutationObserver((evt) => {
        this.parent = this.elem.parentNode;
      });
      observer.observe(this.elem, { attributes: true, childList: true, });
    }
    update(ops) {
      this.options = ops
      return this;
    }
    elem = undefined;
    parent = undefined;
  }

  let refs = [];

  class Component {
    constructor(ops) {
      this.options = ops;
      return this;
    }
    elem = undefined;
    _options = Object.create(null);
    get options() {
      return this._options
    }
    set options(val) {
      this._options = val;
      this.elem = val.render().elem;
      components.push(Object.assign({}, val))
    }
  }

  let components = []

  class Lit {
    constructor(selector) {
      if (typeof selector === 'string') {
        try {
          this.elem = global.document.querySelector(selector);
        } catch (err) {
          console.error(err)
        }
      } else {
        this.elem = selector;
      }
      return this;
    }
    addClass(classIn) {
      if (!this.elem) return;
      let classN = this.elem.className.split(' ');
      if (!classN.includes(classIn)) {
        classN.push(classIn)
        this.elem.className = classN.join(' ')
      }
      return this;
    }
    removeClass(classDel) {
      if (!this.elem) return;
      let classN = this.elem.className.split(' ');
      if (classN.includes(classDel)) {
        classN.forEach((n, i) => n === classDel && classN.splice(i, 1))
        this.elem.className = classN.join(' ')
      }
      return this;
    }
    html(content) {
      if (!this.elem) return;
      if (this.elem && !content) return this.elem.innerHTML;
      this.elem.innerHTML = content;
      return this;
    }
    on(name, callback) {
      if (!this.elem) return;
      this.elem.addEventListener(name, callback);
      return this;
    }
    off(name) {
      if (!this.elem) return;
      this.elem.removeEventListener(name, () => { });
      return this;
    }
    show() {
      if (!this.elem) return;
      this.elem.style.display = 'inline'
      return this;
    }
    hide() {
      if (!this.elem) return;
      this.elem.style.display = 'none'
      return this;
    }
  }

  return {
    createComponent(name, ops) {
      return new Component(Object.assign(ops, { name }))
    },
    createElement() {
      const args = arguments,
        len = args.length;
      const element = new Element(Object.assign(args[1], { tagName: args[0] }))
      for (let i = 0; i < len; i++)
        if (i > 1) {
          const component = args[i].elem;
          element.elem.append(component);
        }
      return element
    },
    createStyleSheet() {
      const args = arguments,
        len = args.length;
      const styleSheet = new StyleSheet(len > 1 ? args[1] : args[0]);
      if (len === 2) {
        const name = args[0];
        const element = new Element({ tagName: 'style', text: `${name}{${styleSheet.style}}` });
        const head = global.document.querySelector('head');
        head.append(element.elem);
      }
      return styleSheet.style
    },
    Ref,
    Refs(name) {
      let elems = [];
      refs.forEach(ref => {
        if (ref.name === name) {
          elems.push(ref.elem)
        }
      })
      return elems;
    },
    components(name) {
      let comps = [];
      components.forEach(component => {
        if (component.name === name) {
          comps.push(component)
        }
      })
      return comps
    },
    query(selector) {
      return new Lit(selector)
    }
  }
});
